package pl.edu.mimuw.irs.core.model.analysis.dd;

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import pl.edu.mimuw.irs.core.model.analysis.RequirementPlace;
import pl.edu.mimuw.irs.core.model.analysis.Time;
import pl.edu.mimuw.irs.core.model.design.IRSActivity;
import pl.edu.mimuw.irs.core.model.design.IRSNodeInterface;
import pl.edu.mimuw.irs.core.model.design.IRSRequirement;
import pl.edu.mimuw.irs.core.model.design.IRSWorkflow;
import pl.edu.mimuw.irs.core.model.design.processing.Requirement;
import pl.edu.mimuw.irs.core.util.Converter;

public class DependenciesGraph {

	// *** Attributes

	private IRSWorkflow workflow;
	private Map<IRSActivity, ActivityWrapper> activities;
	private Map<Requirement, Dependency> nodes;
	private Map<Requirement, RequirementPlace> requirements;
	private Map<IRSActivity, Set<RequirementPlace>> busyRequirements;
	private Time timestamp;
//	private Set<Cycle> cycles;
	// Getters and setters
	public IRSWorkflow getWorkflow() {
		return workflow;
	}
	public Map<IRSActivity, ActivityWrapper> getActivities() {
		if (activities == null) activities = new LinkedHashMap<IRSActivity, ActivityWrapper>();
		return activities;
	}
	public Map<Requirement, Dependency> getNodes() {
		if (nodes == null) nodes = new LinkedHashMap<Requirement, Dependency>();
		return nodes;
	}
	public Map<Requirement, RequirementPlace> getRequirements() {
		if (requirements == null) requirements = new LinkedHashMap<Requirement, RequirementPlace>();
		return requirements;
	}
	public Map<IRSActivity, Set<RequirementPlace>> getBusyRequirements() {
		if (busyRequirements == null) busyRequirements = new LinkedHashMap<IRSActivity, Set<RequirementPlace>>();
		return busyRequirements;
	}
	public Time getTimestamp() {
		if (timestamp == null) timestamp = new Time();
		return timestamp;
	}
	public void setTimestamp(Time timestamp) {
		this.timestamp = timestamp;
	}
//	public Map<Requirement, RequirementPlace> getRequirementPlaces() {
//		if (requirementPlaces == null) requirementPlaces = 
//			new LinkedHashMap<Requirement, RequirementPlace>();
//		return requirementPlaces;
//	}
//	public Set<Cycle> getCycles() {
//		if (this.cycles == null) cycles = new LinkedHashSet<Cycle>();
//		return cycles;
//	}
	
	// *** Constructors
	
	public DependenciesGraph(IRSWorkflow irsWorkflow,
			Map<Requirement, RequirementPlace> requirements) {
		this.workflow = irsWorkflow;
		this.requirements = requirements;
		
		createWrappers(workflow.getActivities());
		Object[] array = getActivities().values().toArray(); // XXX : Delete - used for debug purposes
		array.toString(); 
		
		Map<ActivityWrapper, Dependency> activitiesMap = 
			new LinkedHashMap<ActivityWrapper, Dependency>();
		createNodes(getNodes(), activitiesMap, workflow.getRootPlace());
		//createCycles(getCycles());
		//linkCyclesToNodes(getCycles());
	}
	
	// *** Methods
	
	public boolean isDeadlocking(ActivityWrapper activity) {
		/*
		 * Now we have to check if our requirements of our activity are met. 
		 * If yes - we can start this activity. Otherwise for every requirement, 
		 * which is not met, we have to ask if some other activity using 
		 * it will free it for us later (knowing what requirements we have 
		 * already reserved). 
		 * So first - we check if there is a way for us to finish, without bothering
		 * anyone else.
		 */
		PathNode path = activity.getDescendingPath();
		if (path.canBeCompleted(nodes, false)) {
			return false;
		}
		
		/*
		 * Otherwise we have to launch expensive search which involves asking other
		 * activities about their completing capabilities.
		 */
		return !path.canBeCompleted(nodes, true);
	}
	
	public void start(ActivityWrapper activity, Set<RequirementPlace> set) {
		/*
		 * Here we have starting activity and set of requirements that will be made
		 * busy by this start.
		 */
		for (RequirementPlace requirement : set) {
			Set<ActivityWrapper> working = getNodes().get(requirement.getRequirement()).getWorkingActivities();
			if (!working.contains(activity)) working.add(activity);
		}
		getBusyRequirements().put(activity.getActivity(), set);
		activity.setStarted(true);
	}

	public void end(ActivityWrapper activity) {
		/*
		 * Here we have ending activity and set of requirements that will be made
		 * free by this end.
		 */
		for (RequirementPlace requirement : getBusyRequirements().get(activity.getActivity())) {
			Set<ActivityWrapper> working = getNodes().get(requirement.getRequirement()).getWorkingActivities();
			if (working.contains(activity)) working.remove(activity);
		}
		getBusyRequirements().remove(activity.getActivity());
		activity.setStarted(false);
	}
	
	private void createWrappers(Set<IRSActivity> activities) {
		Map<IRSActivity, ActivityWrapper> wrappers = getActivities();
		for (IRSActivity activity : activities) {
			wrappers.put(activity, new ActivityWrapper(activity));
		}
		
		/*
		 * Creating ancestry tree inside every wrapper.
		 */
		for (ActivityWrapper wrapper : wrappers.values()) {
			wrapper.setRootTimestamp(getTimestamp());
			wrapper.createDescendingPath(wrappers);
		}
	}
	
	private void createNodes(Map<Requirement, Dependency> nodes, 
			Map<ActivityWrapper, Dependency> activitiesMap, IRSNodeInterface nodeInterface) {
		
		if (nodeInterface instanceof IRSActivity) {
			IRSActivity irsActivity = Converter.asIRSActivity(nodeInterface);
			ActivityWrapper activity = getActivities().get(irsActivity);
			Map<Requirement, Dependency> activityDependencies = activity.getDependencies();
			IRSRequirement irsRequirement = irsActivity.getRequirement();
			/* For every requirement defined in this node we look 
			 * if this dependency has already been defined by any other activity. 
			 * If yes - we reuse it, if no - we create it and put it into map.
			 */
			for (Requirement requirement : irsRequirement.getRequirements()) {
				Dependency dependency = nodes.get(requirement);
				if (dependency == null) {
					dependency = new Dependency(getRequirements().get(requirement));
					nodes.put(requirement, dependency);
				}
				// FIXME : check if this works!!!
				activityDependencies.put(requirement, dependency);
				
				/* Now we search for every parent activity that has defined
				 * an activity-dependency pair. We add every such dependency
				 * to our newly created one as a child. This is because the new
				 * node requires all the parent requirements to be fulfilled until
				 * the new one can be accessed. Newly created dependency will be than 
				 * put there. Note that all parents must have been added to this map
				 * earlier because we traverse the fragmentation tree using DFS. Finally,
				 * knowing that we found our parent, we can map new node by the parent
				 * IRSActivity (useful for further operations).
				 */
				for (Entry<ActivityWrapper, Dependency> entry : activitiesMap.entrySet()) {
					if (entry.getKey().isAncestorOf(irsActivity)) {
						Set<Dependency> set = dependency.getDependencies().get(entry.getKey());
						if (set == null) {
							set = new LinkedHashSet<Dependency>();
							//ActivityWrapper wrapper = getActivities().get(entry.getKey());
							dependency.getDependencies().put(entry.getKey(), set);
						}
						set.add(entry.getValue());
					}
				}

				/* Finally we add this set to the activitiesMap for further 
				 * analysis.
				 */
				if (!activitiesMap.containsKey(irsActivity)) {
					activitiesMap.put(activity, dependency);
				}
			}
		}
		
		for (IRSNodeInterface child : nodeInterface.getChildren()) {
			createNodes(nodes, activitiesMap, child);
		}
	}
	
	public void reset() {
		setTimestamp(new Time());
		for (ActivityWrapper activity : activities.values()) {
			activity.reset();
			activity.setRootTimestamp(getTimestamp());
		}
	}
}
